Guide complet pour créer un indicateur de complétion de formulaire en temps réel avec React et le hook useFormStatus pour une UX supérieure.
Maîtriser l'UX des formulaires : Créer un indicateur de pourcentage de complétion dynamique avec le hook useFormStatus de React
Dans le monde du développement web, les formulaires sont le point d'intersection critique où les utilisateurs et les applications échangent des informations. Un formulaire mal conçu peut être un point de friction majeur, conduisant à la frustration de l'utilisateur et à des taux d'abandon élevés. À l'inverse, un formulaire bien conçu est intuitif, utile et encourage sa complétion. L'un des outils les plus efficaces de notre boîte à outils d'expérience utilisateur (UX) pour y parvenir est un indicateur de progression en temps réel.
Ce guide vous plongera en détail dans la création d'un indicateur de pourcentage de complétion de formulaire dynamique en React. Nous explorerons comment suivre la saisie de l'utilisateur en temps réel et, surtout, comment intégrer cela avec les fonctionnalités modernes de React comme le hook useFormStatus pour offrir une expérience fluide du premier caractère saisi à la soumission finale. Que vous construisiez un simple formulaire de contact ou un processus d'inscription complexe en plusieurs étapes, les principes abordés ici vous aideront à créer une interface plus engageante et conviviale.
Comprendre les concepts fondamentaux
Avant de commencer à construire, il est essentiel de comprendre les concepts modernes de React qui constituent le fondement de notre solution. Le hook useFormStatus est intrinsèquement lié aux Composants Serveur React (React Server Components) et aux Actions Serveur (Server Actions), un changement de paradigme dans la manière dont nous gérons les mutations de données et la communication avec le serveur.
Un bref aperçu des Actions Serveur de React
Traditionnellement, la gestion des soumissions de formulaires dans React impliquait du JavaScript côté client. Nous écrivions un gestionnaire onSubmit, empêchions le comportement par défaut du formulaire, collections les données (souvent avec useState), puis passions un appel API en utilisant fetch ou une bibliothèque comme Axios. Ce modèle fonctionne, mais il implique beaucoup de code répétitif.
Les Actions Serveur rationalisent ce processus. Ce sont des fonctions que vous pouvez définir sur le serveur (ou sur le client avec la directive 'use server') et passer directement à la prop action d'un formulaire. Lorsque le formulaire est soumis, React gère automatiquement la sérialisation des données et l'appel API, exécutant la logique côté serveur. Cela simplifie le code côté client et colocalise la logique de mutation avec les composants qui l'utilisent.
Présentation du hook useFormStatus
Lorsqu'une soumission de formulaire est en cours, vous avez besoin d'un moyen de donner un retour à l'utilisateur. La requête est-elle en cours d'envoi ? A-t-elle réussi ? A-t-elle échoué ? C'est précisément à cela que sert useFormStatus.
Le hook useFormStatus fournit des informations sur l'état de la dernière soumission d'un <form> parent. Il retourne un objet avec les propriétés suivantes :
pending: Un booléen qui esttruelorsque le formulaire est en cours de soumission, etfalsesinon. C'est parfait pour désactiver des boutons ou afficher des indicateurs de chargement.data: Un objetFormDatacontenant les données qui ont été soumises. C'est incroyablement utile pour implémenter des mises à jour optimistes de l'interface utilisateur.method: Une chaîne de caractères indiquant la méthode HTTP utilisée pour la soumission (par ex., 'GET' ou 'POST').action: Une référence à la fonction qui a été passée à la propactiondu formulaire.
Règle cruciale : Le hook useFormStatus doit être utilisé à l'intérieur d'un composant qui est un descendant d'un élément <form>. Il ne peut pas être utilisé dans le même composant qui rend la balise <form> elle-même ; il doit se trouver dans un composant enfant.
Le défi : Complétion en temps réel vs. état de soumission
Nous arrivons ici à une distinction clé. Le hook useFormStatus est excellent pour comprendre ce qui se passe pendant et après la soumission d'un formulaire. Il vous indique si le formulaire est 'pending' (en attente).
Cependant, un indicateur de pourcentage de complétion de formulaire concerne l'état du formulaire avant la soumission. Il répond à la question de l'utilisateur : "Quelle partie de ce formulaire ai-je remplie correctement jusqu'à présent ?" C'est une préoccupation côté client qui doit réagir à chaque frappe de touche, clic ou sélection de l'utilisateur.
Par conséquent, notre solution se déroulera en deux parties :
- Gestion de l'état côté client : Nous utiliserons des hooks React standards comme
useStateetuseMemopour suivre les champs du formulaire et calculer le pourcentage de complétion en temps réel. - Gestion de l'état de soumission : Nous utiliserons ensuite
useFormStatuspour améliorer l'UX pendant le processus de soumission effectif, créant une boucle de rétroaction complète de bout en bout pour l'utilisateur.
Implémentation étape par étape : Construire le composant de barre de progression
Passons Ă la pratique et construisons un formulaire d'inscription d'utilisateur qui inclut un nom, un e-mail, un pays et un accord des conditions d'utilisation. Nous ajouterons une barre de progression qui se met Ă jour Ă mesure que l'utilisateur remplit ces champs.
Étape 1 : Définir la structure et l'état du formulaire
D'abord, nous allons configurer notre composant principal avec les champs du formulaire et gérer leur état en utilisant useState. Cet objet d'état sera l'unique source de vérité pour les données de notre formulaire.
// Dans votre fichier de composant React, ex: RegistrationForm.js
'use client'; // Requis pour utiliser des hooks comme useState
import React, { useState, useMemo } from 'react';
const initialFormData = {
fullName: '',
email: '',
country: '',
agreedToTerms: false,
};
export default function RegistrationForm() {
const [formData, setFormData] = useState(initialFormData);
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prevData => ({
...prevData,
[name]: type === 'checkbox' ? checked : value,
}));
};
// ... la logique de calcul et le JSX viendront ici
return (
<form className="form-container">
<h2>Créez votre compte</h2>
{/* La barre de progression sera insérée ici */}
<div className="form-group">
<label htmlFor="fullName">Nom complet</label>
<input
type="text"
id="fullName"
name="fullName"
value={formData.fullName}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="email">Adresse e-mail</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="country">Pays</label>
<select
id="country"
name="country"
value={formData.country}
onChange={handleInputChange}
required
>
<option value="">Sélectionnez un pays</option>
<option value="USA">États-Unis</option>
<option value="CAN">Canada</option>
<option value="GBR">Royaume-Uni</option>
<option value="AUS">Australie</option>
<option value="IND">Inde</option>
</select>
</div>
<div className="form-group-checkbox">
<input
type="checkbox"
id="agreedToTerms"
name="agreedToTerms"
checked={formData.agreedToTerms}
onChange={handleInputChange}
required
/>
<label htmlFor="agreedToTerms">J'accepte les termes et conditions</label>
</div>
{/* Le bouton de soumission sera ajouté plus tard */}
</form>
);
}
Étape 2 : La logique de calcul du pourcentage de complétion
Passons maintenant à la logique centrale. Nous devons définir ce que "complet" signifie pour chaque champ. Pour notre formulaire, les règles sont :
- Nom complet : Ne doit pas ĂŞtre vide.
- Email : Doit ĂŞtre un format d'e-mail valide (nous utiliserons une regex simple).
- Pays : Une valeur doit être sélectionnée (ne doit pas être une chaîne vide).
- Conditions : La case doit être cochée.
Nous allons créer une fonction pour encapsuler cette logique et l'envelopper dans useMemo. C'est une optimisation des performances qui garantit que le calcul ne s'exécute à nouveau que lorsque le formData dont il dépend a changé.
// À l'intérieur du composant RegistrationForm
const completionPercentage = useMemo(() => {
const fields = [
{
key: 'fullName',
isValid: (value) => value.trim() !== '',
},
{
key: 'email',
isValid: (value) => /^\S+@\S+\.\S+$/.test(value),
},
{
key: 'country',
isValid: (value) => value !== '',
},
{
key: 'agreedToTerms',
isValid: (value) => value === true,
},
];
const totalFields = fields.length;
let completedFields = 0;
fields.forEach(field => {
if (field.isValid(formData[field.key])) {
completedFields++;
}
});
return Math.round((completedFields / totalFields) * 100);
}, [formData]);
Ce hook useMemo nous donne maintenant une variable completionPercentage qui sera toujours à jour avec l'état de complétion du formulaire.
Étape 3 : Créer l'interface de la barre de progression dynamique
Créons un composant réutilisable ProgressBar. Il prendra le pourcentage calculé comme prop et l'affichera visuellement.
// ProgressBar.js
import React from 'react';
export default function ProgressBar({ percentage }) {
return (
<div className="progress-container">
<div className="progress-bar" style={{ width: `${percentage}%` }}>
<span className="progress-label">{percentage}% Complété</span>
</div>
</div>
);
}
Et voici un peu de CSS de base pour que ça ait l'air bien. Vous pouvez l'ajouter à votre feuille de style globale.
/* styles.css */
.progress-container {
width: 100%;
background-color: #e0e0e0;
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
}
.progress-bar {
height: 24px;
background-color: #4CAF50; /* Une belle couleur verte */
text-align: right;
color: white;
display: flex;
align-items: center;
justify-content: center;
transition: width 0.5s ease-in-out;
}
.progress-label {
padding: 5px;
font-weight: bold;
font-size: 14px;
}
Étape 4 : Intégrer le tout
Maintenant, importons et utilisons notre ProgressBar dans le composant principal RegistrationForm.
// Dans RegistrationForm.js
import ProgressBar from './ProgressBar'; // Ajustez le chemin d'importation
// ... (à l'intérieur de l'instruction return de RegistrationForm)
return (
<form className="form-container">
<h2>Créez votre compte</h2>
<ProgressBar percentage={completionPercentage} />
{/* ... reste des champs du formulaire ... */}
</form>
);
Avec cela en place, à mesure que vous remplissez le formulaire, vous verrez la barre de progression s'animer fluidement de 0% à 100%. Nous avons résolu avec succès la première moitié de notre problème : fournir un retour en temps réel sur la complétion du formulaire.
Où useFormStatus entre en jeu : Améliorer l'expérience de soumission
Le formulaire est complété à 100%, la barre de progression est pleine, et l'utilisateur clique sur "Soumettre". Que se passe-t-il maintenant ? C'est là que useFormStatus brille, nous permettant de fournir un retour clair pendant le processus de soumission des données.
D'abord, définissons une Action Serveur qui gérera la soumission de notre formulaire. Pour cet exemple, elle simulera simplement un délai réseau.
// Dans un nouveau fichier, ex: 'actions.js'
'use server';
// Simule un délai réseau et traite les données du formulaire
export async function createUser(formData) {
console.log('Action Serveur reçue :', formData.get('fullName'));
// Simule un appel à la base de données ou une autre opération asynchrone
await new Promise(resolve => setTimeout(resolve, 2000));
// Dans une application réelle, vous géreriez les états de succès/erreur
console.log('Création de l\'utilisateur réussie !');
// Vous pourriez rediriger l'utilisateur ou retourner un message de succès
}
Ensuite, nous créons un composant SubmitButton dédié. Rappelez-vous la règle : useFormStatus doit être dans un composant enfant du formulaire.
// SubmitButton.js
'use client';
import { useFormStatus } from 'react-dom';
export default function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Création du compte...' : 'Créer le compte'}
</button>
);
}
Ce simple composant fait tellement de choses. Il s'abonne automatiquement à l'état du formulaire. Lorsqu'une soumission est en cours (pending est à true), il se désactive pour empêcher les soumissions multiples et change son texte pour informer l'utilisateur que quelque chose se passe.
Enfin, nous mettons Ă jour notre RegistrationForm pour utiliser l'Action Serveur et notre nouveau SubmitButton.
// Dans RegistrationForm.js
import { createUser } from './actions'; // Importez l'action serveur
import SubmitButton from './SubmitButton'; // Importez le bouton
// ...
export default function RegistrationForm() {
// ... (tout l'état et la logique existants)
return (
// Passez l'action serveur Ă la prop 'action' du formulaire
<form className="form-container" action={createUser}>
<h2>Créez votre compte</h2>
<ProgressBar percentage={completionPercentage} />
{/* Tous les champs du formulaire restent les mĂŞmes */}
{/* Note : L'attribut 'name' sur chaque input est crucial */}
{/* pour que les Actions Serveur créent l'objet FormData. */}
<div className="form-group">
<label htmlFor="fullName">Nom complet</label>
<input name="fullName" ... />
</div>
{/* ... autres inputs avec l'attribut 'name' ... */}
<SubmitButton />
</form>
);
}
Nous avons maintenant un formulaire moderne et complet. La barre de progression guide l'utilisateur pendant qu'il le remplit, et le bouton de soumission fournit un retour clair et sans ambiguïté pendant le processus de soumission. Cette synergie entre l'état côté client et useFormStatus crée une expérience utilisateur robuste et professionnelle.
Concepts avancés et bonnes pratiques
Gérer la validation complexe avec des bibliothèques
Pour des formulaires plus complexes, écrire la logique de validation manuellement peut devenir fastidieux. Des bibliothèques comme Zod ou Yup vous permettent de définir un schéma pour vos données, qui peut ensuite être utilisé pour la validation.
Vous pouvez intégrer cela dans notre calcul de complétion. Au lieu d'une fonction isValid personnalisée pour chaque champ, vous pourriez essayer d'analyser chaque champ par rapport à sa définition de schéma et de compter les succès.
// Exemple avec Zod (conceptuel)
import { z } from 'zod';
const userSchema = z.object({
fullName: z.string().min(1, 'Le nom est requis'),
email: z.string().email(),
country: z.string().min(1, 'Le pays est requis'),
agreedToTerms: z.literal(true, { message: 'Vous devez accepter les conditions' }),
});
// Dans votre calcul useMemo :
const completedFields = Object.keys(formData).reduce((count, key) => {
const fieldSchema = userSchema.shape[key];
const result = fieldSchema.safeParse(formData[key]);
return result.success ? count + 1 : count;
}, 0);
Considérations sur l'accessibilité (a11y)
Une excellente expérience utilisateur est une expérience accessible. Notre indicateur de progression doit être compréhensible pour les utilisateurs de technologies d'assistance comme les lecteurs d'écran.
Améliorez le composant ProgressBar avec les attributs ARIA :
// ProgressBar.js amélioré
export default function ProgressBar({ percentage }) {
return (
<div
role="progressbar"
aria-valuenow={percentage}
aria-valuemin="0"
aria-valuemax="100"
aria-label={`Complétion du formulaire : ${percentage} pour cent`}
className="progress-container"
>
{/* ... div interne ... */}
</div>
);
}
role="progressbar": Informe la technologie d'assistance que cet élément est une barre de progression.aria-valuenow: Communique la valeur actuelle.aria-valueminetaria-valuemax: Définissent la plage.aria-label: Fournit une description lisible par un humain de la progression.
Pièges courants et comment les éviter
- Utiliser `useFormStatus` au mauvais endroit : L'erreur la plus courante. Rappelez-vous, le composant utilisant ce hook doit ĂŞtre un enfant du
<form>. Encapsuler votre bouton de soumission dans son propre composant est le modèle standard et correct. - Oublier les attributs `name` sur les inputs : Lorsque vous utilisez des Actions Serveur, l'attribut
namen'est pas négociable. C'est ainsi que React construit l'objetFormDataqui est envoyé au serveur. Sans lui, votre action serveur ne recevra aucune donnée. - Confondre la validation client et serveur : Le pourcentage de complétion en temps réel est basé sur la validation côté client pour un retour UX immédiat. Vous devez toujours re-valider les données sur le serveur au sein de votre Action Serveur. Ne faites jamais confiance aux données provenant du client.
Conclusion
Nous avons déconstruit avec succès le processus de création d'un formulaire sophistiqué et convivial dans le React moderne. En comprenant les rôles distincts de l'état côté client et du hook useFormStatus, nous pouvons concevoir des expériences qui guident les utilisateurs, fournissent des retours clairs et augmentent finalement les taux de conversion.
Voici les points clés à retenir :
- Pour un retour en temps réel (avant soumission) : Utilisez la gestion de l'état côté client (
useState) pour suivre les changements des inputs et calculer la progression de la complétion. UtilisezuseMemopour optimiser ces calculs. - Pour un retour sur la soumission (pendant/après soumission) : Utilisez le hook
useFormStatusau sein d'un composant enfant de votre formulaire pour gérer l'interface utilisateur pendant l'état d'attente (par ex., désactiver des boutons, afficher des spinners). - La synergie est la clé : La combinaison de ces deux approches couvre tout le cycle de vie de l'interaction d'un utilisateur avec un formulaire, du début à la fin.
- Donnez toujours la priorité à l'accessibilité : Utilisez les attributs ARIA pour vous assurer que vos composants dynamiques sont utilisables par tout le monde.
En mettant en œuvre ces modèles, vous allez au-delà de la simple collecte de données et commencez à concevoir une conversation avec vos utilisateurs — une conversation claire, encourageante et respectueuse de leur temps et de leurs efforts.